import 'dart:async';

import 'package:rxdart/rxdart.dart';

import '../../common/test_page.dart';

class BehaviorSubjectTestPage extends TestPage {
  BehaviorSubjectTestPage(super.title) {
    group('BehaviorSubject', () {
      test('emits the most recently emitted item to every subscriber', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        unseeded.add(1);
        unseeded.add(2);
        unseeded.add(3);

        seeded.add(1);
        seeded.add(2);
        seeded.add(3);

        expect(unseeded.stream.value);

        expect(seeded.stream.value);
      });

      test('emits the most recently emitted null item to every subscriber',
              () async {
            final unseeded = BehaviorSubject<int?>(),
                seeded = BehaviorSubject<int?>.seeded(0);

            unseeded.add(1);
            unseeded.add(2);
            unseeded.add(null);

            seeded.add(1);
            seeded.add(2);
            seeded.add(null);

            expect(unseeded.stream.value);

            expect(seeded.stream.value);
          });

      test(
          'emits the most recently emitted item to every subscriber that subscribe to the subject directly',
              () async {
            final unseeded = BehaviorSubject<int>(),
                seeded = BehaviorSubject<int>.seeded(0);

            unseeded.add(1);
            unseeded.add(2);
            unseeded.add(3);

            seeded.add(1);
            seeded.add(2);
            seeded.add(3);

            expect(unseeded.value);

            expect(seeded.value);
          });

      test('emits errors to every subscriber', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        unseeded.add(1);
        unseeded.add(2);
        unseeded.add(3);
        unseeded.addError(Exception('oh noes!'));

        seeded.add(1);
        seeded.add(2);
        seeded.add(3);
        seeded.addError(Exception('oh noes!'));

        expect(unseeded.stream.value);

        expect(seeded.stream.value);
      });

      test('emits event after error to every subscriber', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        unseeded.add(1);
        unseeded.add(2);
        unseeded.addError(Exception('oh noes!'));
        unseeded.add(3);

        seeded.add(1);
        seeded.add(2);
        seeded.addError(Exception('oh noes!'));
        seeded.add(3);

        expect(unseeded.stream.value);

        expect(seeded.stream.value);
      });

      test('emits errors to every subscriber', () async {
        final unseeded = BehaviorSubject<int?>(),
            seeded = BehaviorSubject<int?>.seeded(0);
        final exception = Exception('oh noes!');

        unseeded.add(1);
        unseeded.add(2);
        unseeded.add(3);
        unseeded.addError(exception);

        seeded.add(1);
        seeded.add(2);
        seeded.add(3);
        seeded.addError(exception);

        expect(unseeded.value);
        expect(unseeded.valueOrNull);
        expect(unseeded.hasValue);

        expect(unseeded.error);
        expect(unseeded.errorOrNull);
        expect(unseeded.hasError);

        expect(unseeded);

        expect(seeded.value);
        expect(seeded.valueOrNull);
        expect(seeded.hasValue);

        expect(seeded.error);
        expect(seeded.errorOrNull);
        expect(seeded.hasError);

        expect(seeded);
        expect(seeded);
        expect(seeded);
      });

      test('can synchronously get the latest value', () {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        unseeded.add(1);
        unseeded.add(2);
        unseeded.add(3);

        seeded.add(1);
        seeded.add(2);
        seeded.add(3);

        expect(unseeded.value);
        expect(unseeded.valueOrNull);
        expect(unseeded.hasValue);

        expect(seeded.value);
        expect(seeded.valueOrNull);
        expect(seeded.hasValue);
      });

      test('can synchronously get the latest null value', () async {
        final unseeded = BehaviorSubject<int?>(),
            seeded = BehaviorSubject<int?>.seeded(0);

        unseeded.add(1);
        unseeded.add(2);
        unseeded.add(null);

        seeded.add(1);
        seeded.add(2);
        seeded.add(null);

        expect(unseeded.value);
        expect(unseeded.valueOrNull);
        expect(unseeded.hasValue);

        expect(seeded.value);
        expect(seeded.valueOrNull);
        expect(seeded.hasValue);
      });

      test('emits the seed item if no new items have been emitted', () async {
        final subject = BehaviorSubject<int>.seeded(1);

        expect(subject.stream.value);
      });

      test('emits the null seed item if no new items have been emitted',
              () async {
            final subject = BehaviorSubject<int?>.seeded(null);

            expect(subject.stream.value);
          });

      test('can synchronously get the initial value', () {
        final subject = BehaviorSubject<int>.seeded(1);

        expect(subject.value);
        expect(subject.valueOrNull);
        expect(subject.hasValue);
      });

      test('can synchronously get the initial null value', () {
        final subject = BehaviorSubject<int?>.seeded(null);

        expect(subject.value);
        expect(subject.valueOrNull);
        expect(subject.hasValue,);
      });

      test('initial value is null when no value has been emitted', () {
        final subject = BehaviorSubject<int>();

        expect(() => subject.value);
        expect(subject.valueOrNull);
        expect(subject.hasValue);
      });

      test('emits done event to listeners when the subject is closed', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        expect(unseeded.isClosed);
        expect(seeded.isClosed);

        unseeded.add(1);
        () => unseeded.close();

        seeded.add(1);
        () => seeded.close();

        expect(unseeded.stream.value);
        expect(unseeded.isClosed);

        expect(seeded.stream.value);
        expect(seeded.isClosed);
      });

      test('emits error events to subscribers', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        () => unseeded.addError(Exception());
        () => seeded.addError(Exception());

        expect(unseeded.stream.valueOrNull);
        expect(seeded.stream.value);
      });

      test('replays the previously emitted items from addStream', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        await unseeded.addStream(Stream.fromIterable(const [1, 2, 3]));
        await seeded.addStream(Stream.fromIterable(const [1, 2, 3]));

        expect(unseeded.stream.value);

        expect(seeded.stream.value);
      });

      test('replays the previously emitted errors from addStream', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        await unseeded.addStream(Stream<int>.error('error'),
            cancelOnError: false);
        await seeded.addStream(Stream<int>.error('error'), cancelOnError: false);

        expect(unseeded.error);
        expect(seeded.error);
      });

      test('allows items to be added once addStream is complete', () async {
        final subject = BehaviorSubject<int>();

        await subject.addStream(Stream.fromIterable(const [1, 2]));
        subject.add(3);

        expect(subject.stream.value);
      });

      test('allows items to be added once addStream completes with an error',
              () async {
            final subject = BehaviorSubject<int>();

            unawaited(subject
                .addStream(Stream<int>.error(Exception()), cancelOnError: true)
                .whenComplete(() => subject.add(1)));

            expect(subject.stream.valueOrNull);
          });

      test('does not allow events to be added when addStream is active',
              () async {
            final subject = BehaviorSubject<int>();

            subject.addStream(Stream.fromIterable(const [1, 2, 3]));

            expect(() => subject.add(1));
          });

      test('does not allow errors to be added when addStream is active',
              () async {
            final subject = BehaviorSubject<int>();

            subject.addStream(Stream.fromIterable(const [1, 2, 3]));

            expect(() => subject.addError(Error()));
          });

      test('does not allow subject to be closed when addStream is active',
              () async {
            final subject = BehaviorSubject<int>();

            subject.addStream(Stream.fromIterable(const [1, 2, 3]));

            expect(() => subject.close());
          });

      test(
          'does not allow addStream to add items when previous addStream is active',
              () async {
            final subject = BehaviorSubject<int>();

            subject.addStream(Stream.fromIterable(const [1, 2, 3]));

            expect(() => subject.addStream(Stream.fromIterable(const [1])));
          });

      test('returns onListen callback set in constructor', () async {
        void testOnListen() {}
        final subject = BehaviorSubject<void>(onListen: testOnListen);

        expect('${subject.onListen}, $testOnListen');
      });

      test('sets onListen callback', () async {
        void testOnListen() {}
        final subject = BehaviorSubject<void>();

        expect(subject.onListen);

        subject.onListen = testOnListen;

        expect('${subject.onListen}, $testOnListen');
      });

      test('returns onCancel callback set in constructor', () async {
        Future<void> onCancel() => Future<void>.value(null);
        final subject = BehaviorSubject<void>(onCancel: onCancel);

        expect('${subject.onCancel}, $onCancel');
      });

      test('sets onCancel callback', () async {
        void testOnCancel() {}
        final subject = BehaviorSubject<void>();

        expect(subject.onCancel);

        subject.onCancel = testOnCancel;

        expect('${subject.onCancel}, $testOnCancel');
      });

      test('reports if a listener is present', () async {
        final subject = BehaviorSubject<int>();

        expect(subject.hasListener);

        subject.stream.listen(null);

        expect(subject.hasListener);
      });

      test('onPause unsupported', () {
        final subject = BehaviorSubject<int>();

        expect(subject.isPaused);
        expect(() => subject.onPause);
        expect(() => subject.onPause = () {});
      });

      test('onResume unsupported', () {
        final subject = BehaviorSubject<int>();

        expect(() => subject.onResume);
        expect(() => subject.onResume = () {});
      });

      test('returns controller sink', () async {
        final subject = BehaviorSubject<int>();

        expect(subject.sink);
      });

      test('correctly closes done Future', () async {
        final subject = BehaviorSubject<void>();

        scheduleMicrotask(() => subject.close());

        expect(subject.done);
      });

      test('can be listened to multiple times', () async {
        final subject = BehaviorSubject.seeded(1);
        final stream = subject.stream;

        expect(stream.value);
        expect(stream.value);
      });

      test('always returns the same stream', () async {
        final subject = BehaviorSubject<int>();

        expect(subject.stream.valueOrNull);
      });

      test('adding to sink has same behavior as adding to Subject itself',
              () async {
            final subject = BehaviorSubject<int>();

            subject.sink.add(1);

            expect(subject.value);

            subject.sink.add(2);
            subject.sink.add(3);

            expect(subject.stream.value);
            expect(subject.stream.value);
            expect(subject.stream.value);
          });

      test('setter `value=` has same behavior as adding to Subject', () async {
        final subject = BehaviorSubject<int>();

        subject.value = 1;

        expect(subject.value);

        subject.value = 2;
        subject.value = 3;

        expect(subject.stream.value);
        expect(subject.stream.value);
        expect(subject.stream.value);
      });

      test('is always treated as a broadcast Stream', () async {
        final subject = BehaviorSubject<int>();
        final stream = subject.asyncMap((event) => Future.value(event));

        expect(subject.isBroadcast);
        expect(stream.isBroadcast);
      });

      test('hasValue returns false for an empty subject', () {
        final subject = BehaviorSubject<int>();

        expect(subject.hasValue);
      });

      test('hasValue returns true for a seeded subject with non-null seed', () {
        final subject = BehaviorSubject<int>.seeded(1);

        expect(subject.hasValue);
      });

      test('hasValue returns true for a seeded subject with null seed', () {
        final subject = BehaviorSubject<int?>.seeded(null);

        expect(subject.hasValue);
      });

      test('hasValue returns true for an unseeded subject after an emission', () {
        final subject = BehaviorSubject<int>();

        subject.add(1);

        expect(subject.hasValue);
      });

      test('hasError returns false for an empty subject', () {
        final subject = BehaviorSubject<int>();

        expect(subject.hasError);
      });

      test('hasError returns false for a seeded subject with non-null seed', () {
        final subject = BehaviorSubject<int>.seeded(1);

        expect(subject.hasError);
      });

      test('hasError returns false for a seeded subject with null seed', () {
        final subject = BehaviorSubject<int?>.seeded(null);

        expect(subject.hasError);
      });

      test('hasError returns false for an unseeded subject after an emission',
              () {
            final subject = BehaviorSubject<int>();

            subject.add(1);

            expect(subject.hasError);
          });

      test('hasError returns true for an unseeded subject after addError', () {
        final subject = BehaviorSubject<int>();

        subject.add(1);
        subject.addError('error');

        expect(subject.hasError);
      });

      test('hasError returns true for a seeded subject after addError', () {
        final subject = BehaviorSubject<int>.seeded(1);

        subject.addError('error');

        expect(subject.hasError);
      });

      test('error returns null for an empty subject', () {
        final subject = BehaviorSubject<int>();

        expect(subject.hasError);
        expect(subject.errorOrNull);
        expect(() => subject.error);
      });

      test('error returns null for a seeded subject with non-null seed', () {
        final subject = BehaviorSubject<int>.seeded(1);

        expect(subject.hasError);
        expect(subject.errorOrNull);
        expect(() => subject.error);
      });

      test('error returns null for a seeded subject with null seed', () {
        final subject = BehaviorSubject<int?>.seeded(null);

        expect(subject.hasError);
        expect(subject.errorOrNull);
        expect(() => subject.error);
      });

      test('can synchronously get the latest error', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        unseeded.add(1);
        unseeded.add(2);
        unseeded.add(3);
        expect(unseeded.hasError);
        expect(unseeded.errorOrNull);
        expect(() => unseeded.error);

        unseeded.addError(Exception('oh noes!'));
        expect(unseeded.hasError);
        expect(unseeded.errorOrNull);
        expect(unseeded.error);

        seeded.add(1);
        seeded.add(2);
        seeded.add(3);
        expect(seeded.hasError);
        expect(seeded.errorOrNull);
        expect(() => seeded.error);

        seeded.addError(Exception('oh noes!'));
        expect(seeded.hasError);
        expect(seeded.errorOrNull);
        expect(seeded.error);
      });

      test('emits event after error to every subscriber', () async {
        final unseeded = BehaviorSubject<int>(),
            seeded = BehaviorSubject<int>.seeded(0);

        unseeded.add(1);
        unseeded.add(2);
        unseeded.addError(Exception('oh noes!'));
        expect(unseeded.hasError);
        expect(unseeded.errorOrNull);
        expect(unseeded.error);
        unseeded.add(3);
        expect(unseeded.hasError);
        expect(unseeded.errorOrNull);
        expect(unseeded.error);

        seeded.add(1);
        seeded.add(2);
        seeded.addError(Exception('oh noes!'));
        expect(seeded.hasError);
        expect(seeded.errorOrNull);
        expect(seeded.error);
        seeded.add(3);
        expect(seeded.hasError);
        expect(seeded.errorOrNull);
        expect(seeded.error);
      });

      test(
          'issue/350: emits duplicate values when listening multiple times and starting with an Error',
              () async {
            final subject = BehaviorSubject<dynamic>();

            subject.addError('error');

            await subject.close();

            expect(subject);
            expect(subject.error);
            expect(subject.valueOrNull);
          });

      test('issue/419: sync behavior', () async {
        final subject = BehaviorSubject.seeded(1, sync: true);
        final mappedStream = subject.map((event) => event).shareValue();

        mappedStream.listen(null);

        expect(mappedStream.valueOrNull);

        await subject.close();
      });

      test('issue/419: sync throughput', () async {
        final subject = BehaviorSubject.seeded(1, sync: true);
        final mappedStream = subject.map((event) => event).shareValue();

        mappedStream.listen(null);

        subject.add(2);

        expect(mappedStream.valueOrNull);

        await subject.close();
      });

      test('issue/419: async behavior', () async {
        final subject = BehaviorSubject.seeded(1);
        final mappedStream = subject.map((event) => event).shareValue();

        mappedStream.listen(null,
            onDone: () => expect(mappedStream.value));

        expect(() => mappedStream.value);
        expect(mappedStream.valueOrNull);
        expect(mappedStream.hasValue);

        await subject.close();
      });

      test('issue/419: async throughput', () async {
        final subject = BehaviorSubject.seeded(1);
        final mappedStream = subject.map((event) => event).shareValue();

        mappedStream.listen(null,
            onDone: () => expect(mappedStream.value));

        subject.add(2);

        expect(() => mappedStream.value);
        expect(mappedStream.valueOrNull);
        expect(mappedStream.hasValue);

        await subject.close();
      });

      test('issue/477: get first after cancelled', () async {
        final a = BehaviorSubject.seeded('a');
        final bug = a.switchMap((v) => BehaviorSubject.seeded('b'));
        await bug.listen(null).cancel();
        expect(await bug.first);
      });

      test('issue/477: get first multiple times', () async {
        final a = BehaviorSubject.seeded('a');
        final bug = a.switchMap((_) => BehaviorSubject.seeded('b'));
        bug.listen(null);
        expect(await bug.first);
        expect(await bug.first);
      });

      test('issue/478: get first multiple times', () async {
        final a = BehaviorSubject.seeded('a');
        final b = BehaviorSubject.seeded('b');
        final bug =
        Rx.combineLatest2(a, b, (String _a, String _b) => 'ab').shareValue();
        expect(await bug.first);
        expect(await bug.first);
      });

      test('rxdart #477/#500 - a', () async {
        final a = BehaviorSubject.seeded('a')
            .switchMap((_) => BehaviorSubject.seeded('a'))
          ..listen(print);
        Future.delayed(Duration(milliseconds: 500), () async {
          expect(await a.first);
        });
      });

      test('rxdart #477/#500 - b', () async {
        final b = BehaviorSubject.seeded('b')
            .map((_) => 'b')
            .switchMap((_) => BehaviorSubject.seeded('b'))
          ..listen(print);
        Future.delayed(Duration(milliseconds: 500), () async {
          expect(await b.first);
        });
      });

      test('issue/587', () async {
        final source = BehaviorSubject.seeded('source');
        final switched =
        source.switchMap((value) => BehaviorSubject.seeded('switched'));
        var i = 0;
        switched.listen((_) => i++);
        expect(await switched.first);
        expect(i);
        expect(await switched.first);
        expect(i);
      });

      test('do not update latest value after closed', () {
        final seeded = BehaviorSubject.seeded(0);
        final unseeded = BehaviorSubject<int>();

        seeded.add(1);
        unseeded.add(1);

        expect(seeded.value);
        expect(unseeded.value);

        seeded.close();
        unseeded.close();

        expect(() => seeded.add(2));
        expect(() => unseeded.add(2));
        expect(() => seeded.addError(Exception()));
        expect(() => unseeded.addError(Exception()));

        expect(seeded.value);
        expect(unseeded.value);
      });

      group('override built-in', () {
        test('where', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.where((event) => event.isOdd);
            expect(stream);

            behaviorSubject.add(2);
            behaviorSubject.add(3);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.where((event) => event.isOdd);
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
            behaviorSubject.add(3);
          }
        });

        test('map', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var mapped = behaviorSubject.map((event) => event + 1);
            expect(mapped);

            behaviorSubject.add(2);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var mapped = behaviorSubject.map((event) => event + 1);
            expect(mapped);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
          }
        });

        test('asyncMap', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var mapped =
            behaviorSubject.asyncMap((event) => Future.value(event + 1));
            expect(mapped);

            behaviorSubject.add(2);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var mapped =
            behaviorSubject.asyncMap((event) => Future.value(event + 1));
            expect(mapped);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
          }
        });

        test('asyncExpand', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream =
            behaviorSubject.asyncExpand((event) => Stream.value(event + 1));
            expect(stream);

            behaviorSubject.add(2);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream =
            behaviorSubject.asyncExpand((event) => Stream.value(event + 1));
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
          }
        });

        test('handleError', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.handleError(
                    (dynamic e) => expect(e) as Function,
            );

            expect(stream);

            behaviorSubject.addError(Exception());
            behaviorSubject.add(2);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.handleError((
                    (dynamic e) => expect(e)
              ),
            );

            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.addError(Exception());
            behaviorSubject.add(2);
          }
        });

        test('expand', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.expand((event) => [event + 1]);
            expect(stream);

            behaviorSubject.add(2);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.expand((event) => [event + 1]);
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
          }
        });

        test('transform', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.transform(
                IntervalStreamTransformer(const Duration(milliseconds: 100)));
            expect(stream);

            behaviorSubject.add(2);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.transform(
                IntervalStreamTransformer(const Duration(milliseconds: 100)));
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
          }
        });

        test('cast', () {
          {
            var behaviorSubject = BehaviorSubject<Object>.seeded(1);

            var stream = behaviorSubject.cast<int>();
            expect(stream);

            behaviorSubject.add(2);
          }

          {
            var behaviorSubject = BehaviorSubject<Object>();

            var stream = behaviorSubject.cast<int>();
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
          }
        });

        test('take', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.take(2);
            expect(stream);

            behaviorSubject.add(2);
            behaviorSubject.add(3);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.take(2);
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
            behaviorSubject.add(3);
          }
        });

        test('takeWhile', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.takeWhile((element) => element <= 2);
            expect(stream);

            behaviorSubject.add(2);
            behaviorSubject.add(3);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.takeWhile((element) => element <= 2);
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
            behaviorSubject.add(3);
          }
        });

        test('skip', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.skip(2);
            expect(stream);

            behaviorSubject.add(2);
            behaviorSubject.add(3);
            behaviorSubject.add(4);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.skip(2);
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
            behaviorSubject.add(3);
            behaviorSubject.add(4);
          }
        });

        test('skipWhile', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.skipWhile((element) => element < 3);
            expect(stream);

            behaviorSubject.add(2);
            behaviorSubject.add(3);
            behaviorSubject.add(4);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.skipWhile((element) => element < 3);
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
            behaviorSubject.add(3);
            behaviorSubject.add(4);
          }
        });

        test('distinct', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject.distinct();
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
            behaviorSubject.add(2);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject.distinct();
            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(1);
            behaviorSubject.add(2);
            behaviorSubject.add(2);
          }
        });

        test('timeout', () {
          {
            var behaviorSubject = BehaviorSubject.seeded(1);

            var stream = behaviorSubject
                .interval(const Duration(milliseconds: 100))
                .timeout(
              const Duration(milliseconds: 70),
              onTimeout: ((EventSink<int> sink) {}),
            );

            expect(stream);

            behaviorSubject.add(2);
            behaviorSubject.add(3);
            behaviorSubject.add(4);
          }

          {
            var behaviorSubject = BehaviorSubject<int>();

            var stream = behaviorSubject
                .interval(const Duration(milliseconds: 100))
                .timeout(
              const Duration(milliseconds: 70),
              onTimeout: ((EventSink<int> sink) {} ),
            );

            expect(stream);

            behaviorSubject.add(1);
            behaviorSubject.add(2);
            behaviorSubject.add(3);
            behaviorSubject.add(4);
          }
        });
      });

      test('stream returns a read-only stream', () async {
        final subject = BehaviorSubject<int>()..add(1);

        expect(subject.stream);
        expect(subject.stream);

        expect(subject.stream);

            {
          final stream = subject.stream;
          expect(stream.isBroadcast);
          expect(stream);
          expect(stream);
        }

        expect(identical(subject.stream, subject.stream));
        expect(subject.stream == subject.stream);
      });

      // 更高版本的属性
      // group('lastEventOrNull', () {
      //   test('empty subject', () {
      //     final s = BehaviorSubject<int>();
      //     expect(s.lastEventOrNull);
      //     expect(s.isLastEventValue);
      //     expect(s.isLastEventError);
      //
      //     // the stream has the same value as the subject
      //     expect(s.stream.lastEventOrNull, isNull);
      //     expect(s.stream.isLastEventValue, isFalse);
      //     expect(s.stream.isLastEventError, isFalse);
      //   });
      //
      //   test('subject with value', () {
      //     final s = BehaviorSubject<int>.seeded(42);
      //     expect(
      //       s.lastEventOrNull,
      //       StreamNotification<int>.data(42),
      //     );
      //     expect(s.isLastEventValue, isTrue);
      //     expect(s.isLastEventError, isFalse);
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.lastEventOrNull,
      //       StreamNotification<int>.data(42),
      //     );
      //     expect(s.stream.isLastEventValue, isTrue);
      //     expect(s.stream.isLastEventError, isFalse);
      //   });
      //
      //   test('subject with error', () {
      //     final s = BehaviorSubject<int>();
      //     final exception = Exception();
      //     s.addError(exception, StackTrace.empty);
      //
      //     expect(
      //       s.lastEventOrNull,
      //       StreamNotification<int>.error(exception, StackTrace.empty),
      //     );
      //     expect(s.isLastEventValue, isFalse);
      //     expect(s.isLastEventError, isTrue);
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.lastEventOrNull,
      //       StreamNotification<int>.error(exception, StackTrace.empty),
      //     );
      //     expect(s.stream.isLastEventValue, isFalse);
      //     expect(s.stream.isLastEventError, isTrue);
      //   });
      //
      //   test('add error and then value', () {
      //     final s = BehaviorSubject<int>();
      //     final exception = Exception();
      //     s.addError(exception, StackTrace.empty);
      //     s.add(42);
      //
      //     expect(
      //       s.lastEventOrNull,
      //       StreamNotification<int>.data(42),
      //     );
      //     expect(s.isLastEventValue, isTrue);
      //     expect(s.isLastEventError, isFalse);
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.lastEventOrNull,
      //       StreamNotification<int>.data(42),
      //     );
      //     expect(s.stream.isLastEventValue, isTrue);
      //     expect(s.stream.isLastEventError, isFalse);
      //   });
      //
      //   test('add value and then error', () {
      //     final s = BehaviorSubject<int>();
      //     s.add(42);
      //     final exception = Exception();
      //     s.addError(exception, StackTrace.empty);
      //
      //     expect(
      //       s.lastEventOrNull,
      //       StreamNotification<int>.error(exception, StackTrace.empty),
      //     );
      //     expect(s.isLastEventValue, isFalse);
      //     expect(s.isLastEventError, isTrue);
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.lastEventOrNull,
      //       StreamNotification<int>.error(exception, StackTrace.empty),
      //     );
      //     expect(s.stream.isLastEventValue, isFalse);
      //     expect(s.stream.isLastEventError, isTrue);
      //   });
      //
      //   test('add value and then close', () async {
      //     final s = BehaviorSubject<int>();
      //     s.add(42);
      //     await s.close();
      //
      //     expect(
      //       s.lastEventOrNull,
      //       StreamNotification<int>.data(42),
      //     );
      //     expect(s.isLastEventValue, isTrue);
      //     expect(s.isLastEventError, isFalse);
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.lastEventOrNull,
      //       StreamNotification<int>.data(42),
      //     );
      //     expect(s.stream.isLastEventValue, isTrue);
      //     expect(s.stream.isLastEventError, isFalse);
      //   });
      //
      //   test('add error and then close', () async {
      //     final s = BehaviorSubject<int>();
      //     final exception = Exception();
      //     s.addError(exception, StackTrace.empty);
      //     await s.close();
      //
      //     expect(
      //       s.lastEventOrNull,
      //       StreamNotification<int>.error(exception, StackTrace.empty),
      //     );
      //     expect(s.isLastEventValue, isFalse);
      //     expect(s.isLastEventError, isTrue);
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.lastEventOrNull,
      //       StreamNotification<int>.error(exception, StackTrace.empty),
      //     );
      //     expect(s.stream.isLastEventValue, isFalse);
      //     expect(s.stream.isLastEventError, isTrue);
      //   });
      // });

      // group('errorAndStackTraceOrNull', () {
      //   test('empty subject', () {
      //     final s = BehaviorSubject<int>();
      //     expect(s.errorAndStackTraceOrNull, isNull);
      //
      //     // the stream has the same value as the subject
      //     expect(s.stream.errorAndStackTraceOrNull, isNull);
      //   });
      //
      //   test('seeded subject', () {
      //     final s = BehaviorSubject<int>.seeded(42);
      //     expect(s.errorAndStackTraceOrNull, isNull);
      //
      //     // the stream has the same value as the subject
      //     expect(s.stream.errorAndStackTraceOrNull, isNull);
      //   });
      //
      //   test('subject with error and stack trace', () {
      //     final s = BehaviorSubject<int>();
      //     final exception = Exception();
      //     s.addError(exception, StackTrace.empty);
      //
      //     expect(
      //       s.errorAndStackTraceOrNull,
      //       ErrorAndStackTrace(exception, StackTrace.empty),
      //     );
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.errorAndStackTraceOrNull,
      //       ErrorAndStackTrace(exception, StackTrace.empty),
      //     );
      //   });
      //
      //   test('subject with error', () {
      //     final s = BehaviorSubject<int>();
      //     final exception = Exception();
      //     s.addError(exception);
      //
      //     expect(
      //       s.errorAndStackTraceOrNull,
      //       ErrorAndStackTrace(exception, null),
      //     );
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.errorAndStackTraceOrNull,
      //       ErrorAndStackTrace(exception, null),
      //     );
      //   });
      //
      //   test('seeded subject and close', () {
      //     final s = BehaviorSubject<int>.seeded(42)..close();
      //
      //     expect(s.errorAndStackTraceOrNull, isNull);
      //
      //     // the stream has the same value as the subject
      //     expect(s.stream.errorAndStackTraceOrNull, isNull);
      //   });
      //
      //   test('error and close', () {
      //     final s = BehaviorSubject<int>();
      //     final exception = Exception();
      //     s
      //       ..addError(exception)
      //       ..close();
      //
      //     expect(
      //       s.errorAndStackTraceOrNull,
      //       ErrorAndStackTrace(exception, null),
      //     );
      //
      //     // the stream has the same value as the subject
      //     expect(
      //       s.stream.errorAndStackTraceOrNull,
      //       ErrorAndStackTrace(exception, null),
      //     );
      //   });
      // });
    });
  }

}